DirectDraw Setup and Tear Down




In this tutorial, I will explain how to go about setting up the DirectDraw interface and then taking it down again when your program is finished running.

I am going to assume you already know Windows programming, how to create a window, how to use WinMain, how to set up a windows message function like WinProc and other various windows tools.

The first thing you want to do is make sure to include the neccessary files in your project.
These files are:
ddraw.h (a header file from DirectX SDK, simple to include)
ddraw.lib (a library file, add to project from DirectX SDK)

If you don't have these files on your computer, visit here to get them. They are available at Microsoft's MSDN site (http://msdn.microsoft.com)

Next, we are going to set up some global variables to hold our basic DirectDraw objects.

// A long pointer to a DirectDraw Object, used for communicating with DirectDraw
LPDD lpdd;
// DD Surface description, used to set up surfaces to draw on
DDSURFACEDESC ddsd;
// A pointer to a DD Surface, the primary one (meaning the screen)
LPDIRECTDRAWSURFACE lpddsprimary;
// Another pointer to a surface, this time the backbuffer
LPDIRECTDRAWSURFACE lpddssecondary;

Some of these may look a little weird if you're not used to Hungarian notation yet. The DD (dd) on each variable stands for DirectDraw. This is important since you will one day hopefully be using DirectInput, DirectSound, etc. which use very similar variables. The LP (lp) part stands for Long Pointer, a prefix so that you always know what kind of variable you're working with. Now, back to the actual content.
At this point you're probably saying to yourself "So now I've got everything set up, when do I start actually USING this stuff in my programs?" Well, right now.

Somewhere in your initialization function (I assume you have one. If you don't, make one cause you'll need it), declare an HRESULT used to store status values. DirectDraw functions return an HRESULT which you can (and should) use to test to see if everything went well. Something like this would work quite well:
HRESULT DDReturnValue;

These HRESULT values should be (if everything works) equal to DD_OK, a DirectDraw defined constant. When you check to make sure something worked, check for equality with DD_OK.

Now, here is the first function you will use:
HRESULT DirectDrawCreate(GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpdirectdraw, IUnknown FAR *pUnkOuter)

And this is where you say "WHOA!! What is THAT?!?!?" Now hold on a second. This really isn't as bad as it looks, as I will quickly explain. This will create the DirectDraw interface that you use to communicate with DirectDraw. It returns an HRESULT (hopefully DD_OK) and takes 3 values.
The first and third will always be NULL. The second will be the address of your DirectDraw pointer, so it ends up looking like this:
DDReturnValue = DirectDrawCreate(NULL, &lpdd, NULL);

Make sure to retrieve the return value of this (and all other) DirectDraw function and check for a DD_OK. It simplifies EVERYTHING if you know what works and what doesn't.

Okay, you now have a pointer to a DirectDraw interface. Next, we will use this pointer to set DirectDraw's cooperation level with windows, a must if you want your programs to run in Windows. I know, I know, nobody WANTS to communicate with Windows, but it's unfortunately necessary for DirectX to work right.

This function works like this:
HRESULT LPDD->SetCooperativeLevel(HWND hWnd, DWORD dwflags)

The "LPDD->" part means that this is a member function of the DirectDraw interface class. If you aren't sure exactly what that means, check out classes, or simply use it how I tell you and you can learn it later. Anyway, this returns an HRESULT (DD_OK hopefully) and takes two values.
The first value is the handle to your window, you know, the one you got when you created your window in the first place. The second value is the flags to tell Windows what level of control you want.
The only flags I really ever use are some of the following:
DDSCL_ALLOWMODEX
DDSCL_FULLSCREEN
DDSCL_EXCLUSIVE
DDSCL_ALLOWREBOOT

The first three flags deal with the program if you want DirectDraw in fullscreen mode. From there, those flags allow you to use Mode X (320x240x8) and use all system resources exclusively.
The last flag allows the user to use CTRL-ALT-DEL to exit if something screws up. It is good practice to almost always include this flag as most users get slightly ticked if they can't control their computer...
To use more than one flag at a time, use the bitwise OR operator ( | ) between each one.
So if you wanted to have a fullscreen application, you would call this function like this (assuming "hwnd" is your window handle):

DDReturnValue = lpdd->SetCooperativeLevel(hwnd, DDSCL_FULLSCREEN | DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT);

Okay, so far we have created our DD interface, set our cooperation with Windows (dang it) and we now get to change the screen resolution so we can get working! Before I start this, however, know that you can only change the display resolution in fullscreen mode, what we did right above. So if you want to change resolution, add in the DDSCL_FULLSCREEN. Here's the function for changing to fullscreen:
HRESULT LPDD->SetDisplayMode(DWORD dwWidth, DWORD dwHeight, DWORD dwBPP, DWORD dwRefreshRate, DWORD dwFlags);

Now THAT is a long function. Once again, the DWORD is a Microsoft Double Word, with the dw being Hungarian notation. But enough about that. We are indeed fortunate, since we have a nice simple function to easily change our screen resolution. Here's the explanation for how you use it. The first value, dwWidth, is the width of your desired resolution in pixels. The next, dwHeight is simply the height of that resolution. Width and height are traditionally held in the ration 4:3, but if you want to experiment, go ahead. The third value is the bits per pixel. This is limited to 8, 16, 24 or 32 right now. Maybe in the future they will have 64-bit mode, but why?!? The last two values are more advanced, so you can generally just leave them out, since they have default values that they will be set to when you leave them out. Here is how you would set a display mode of 800 x 600 x 32-bit true color:
DDReturnValue = lpdd->SetDisplayMode(800, 600, 32);

Yes, it really is that simple. You now have a program which creates DirectDraw, cooperates with Windows (ick) and then changes the screen resolution to whatever your heart desires (within reason, of course).
Now we're heading into that oh-so-fun part where we set up the surfaces that DirectDraw uses to draw items on. You can think of them as pieces of paper that you can draw on, if that helps. For this part, we'll be using the other three global variables that we declared up above: ddsd, lpddsprimary, lpddssecondary. The "lpdds" part on the last two stands for "Long Pointer to a DirectDraw Surface", just so you know. Also, I'm going to be doing all of this assuming you're using DirectDraw in fullscreen mode. Later on I'll set up a tutorial on windowed mode. But since I think that is harder, we'll save it till later.
First, the prototype for the CreateSurface function:
// Set size of structure
ddsd.dwSize = sizeof(ddsd);
// Set up for using ddsCaps
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT;
// Set up surface flags
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_FLIP | DDSCAPS_COMPLEX | DDSCAPS_SYSTEMMEMORY;
// Set up backbuffer ability
ddsd.dwBackBufferCount = 1;
// Create the actual surface
lpdd->CreateSurface(&ddsd, &lpddsprimary, NULL);

Now the play by play of what is going on here. You can kinda get it from the comments, but I'll got through it more in depth. First of all, the variable we are using is DirectDraw Surface Description (hence ddsd). We set the dwSize variable to the size of the structure. This is VERY IMPORTANT! Practically all DirectX structures you will use need you to manually set the size of the structure, so remember that. Next we set the flags for this structure. DDSD_CAPS is always included because it lets you use the ddsCaps sub-structure. DDSD_BACKBUFFERCOUNT tells DirectDraw that we will have a backbuffer or offscreen surface that we can write to and not have it show up till we want it to. Next we actually use that ddsCaps structure to set the surface properties. DDSCAPS_PRIMARYSURFACE tells DirectDraw that this will be what shows up on the screen. DDSCAPS_FLIP tells DirectDraw that we want to be able to quickly flip the backbuffer onto the primary surface for easy viewing. DDSCAPS_COMPLEX is useful if you have more than 1 backbuffer so that we can flip a complex chain of surface, hence the name of the flag. The last flag, DDSCAPS_SYSTEMMEMORY, tells DirectDraw to create this surface in system memory. While video memory is normally faster than system memory, system memory is quite a bit larger than video memory, so you can use video memory for other stuff. Next is the dwBackBufferCount, a value that tells DirectDraw how many back buffers we will be creating later. And lastly, we finally use the CreateSurface function to make the primary surface. We now have a primary surface to draw on. Next, the backbuffer creation:
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
lpddsprimary->GetAttachedSurface(&ddsd.ddsCaps, &lpddssecondary);

For the backbuffer creation, all you need are these two lines of code. The first line sets up the properties of this surface, which is the backbuffer. The next line sets up the buffer as a back surface to the primary (screen) surface.
In later tutorials I'll show you how to actually DO stuff with your these surfaces. But for now, let's go ahead and take this all down, so we're not sucking up system resouces after the program is terminated.

System cleanup using DirectDraw is really quite easy. Since DirectDraw is COM (Component Object Model) based, and Microsoft's programmers paid close attention to the design of each piece to make sure they were similar, we have a special function for each object that lets us tidy up.
Here's how you use it:
HRESULT DirectDrawObject->Release();
DirectDrawObject = NULL;

This is the general format you will use. It's quick, easy and the object is released back for your computer to use. You will, however, want to make sure that your object exists before you release it, otherwise, it could get interesting.
Try this for releasing the DirectDraw interface and both surfaces:
if (lpddssecondary)
{
	lpddssecondary->Release();
	lpddssecondary = NULL;
}
if (lpddsprimary)
{
	lpddsprimary->Release();
	lpddsprimary = NULL;
}
if (lpdd)
{
	lpdd->Release();
	lpdd = NULL;
}

Notice how I checked to see if the object existed before releasing the object. Also, the Release function returns an HRESULT, but since we're quitting anyway, it doesn't do us any good, so don't worry about it.
Okay, I think I'm out of stuff for this tutorial. Hope it was informative and educational for you. There won't be any tasks after this tutorial, given the difficulty of expanding on what was in this one. Just make sure you understand this, since the next DirectDraw tutorial will build on this one significantly. Thanks alot!